#!/usr/bin/perl

# searches for .o files in the current directory and examines each for
# defined functions (T) and undefined references (U) and lists
# anything that isn't required by something else or itself

# useful calling sorted by file:
#   perl minimal/find-unused-code | sort 

use File::Find;
use Data::Dumper;
use strict;
use Getopt::GUI::Long;

my %opts;

LocalGetOptions(\%opts,
		['g|grep',      "run find/grep to look for string usages"],
    );

my (@files, %defined, %lookingfor);

find(\&dot_os, ".");

foreach my $file (@files) {
    collect_from_file($file);
}


if ($#ARGV > -1) {
    print "Dumping found symbol usages:\n\n";
    foreach my $arg (@ARGV) {
	print "$arg:\n";
	print "  ", join("\n  ", @{$defined{$arg}{'usages'}}),"\n\n";
    }
    exit;
}

foreach my $function (sort { $defined{$a}{'source'} cmp $defined{$b}{'source'} } keys(%defined)) {
    if (exists($defined{$function}{'source'}) &&
	!exists($defined{$function}{'usages'})) {
	if (check_for_once_only($defined{$function}{'source'}, $function)) {
	    printf("%-38s %-38s\n", $defined{$function}{'source'}, $function);
	    if ($opts{'g'}) {
		open(P,"find . -regex '.*.\\(c\\|xs\\)' | xargs grep -n $function|");
		while(<P>) {
		    print "  $_";
		}
		close(P);
		print "\n";
	    }
	}
    }
}

sub dot_os {
    return if (!/\.o$/);
    return if ($File::Find::name =~ /\/.libs\//);
#    return if ($File::Find::name =~ /\/(perl|python)\//);
    push @files, $File::Find::name;
}

sub collect_from_file {
    my $file = shift;
#    print "searching $file\n";
    open(I,"nm $file|");
    while(<I>) {
	$defined{$1}{'source'} = $file if (/ T (.*)/);
	push @{$defined{$1}{'usages'}}, $file if (/ U (.*)/);
    }
    close(I);
}
	
sub check_for_once_only {
    my ($file, $function) = @_;
    $file =~ s/\.o/.c/;
    open(I, $file);
    my $usages = 0;
    while(<I>) {
	if (/$function/) {
	    $usages++;
	}
	if ($usages > 1) {
	    close(I);
	    return 0;
	}
    }
    return 1;
}


sub LocalGetOptions {
    if (eval {require Getopt::GUI::Long;}) {
        import Getopt::GUI::Long;
        # optional configure call
        Getopt::GUI::Long::Configure(qw(display_help no_ignore_case capture_output no_gui allow_zero));
        return GetOptions(@_);
    }
    require Getopt::Long;
    import Getopt::Long;
    # optional configure call
    Getopt::Long::Configure(qw(display_help no_ignore_case capture_output));
    GetOptions(LocalOptionsMap(@_));
}

sub LocalOptionsMap {
    my ($st, $cb, @opts) = ((ref($_[0]) eq 'HASH') 
                            ? (1, 1, $_[0]) : (0, 2));
    for (my $i = $st; $i <= $#_; $i += $cb) {
        if ($_[$i]) {
            next if (ref($_[$i]) eq 'ARRAY' && $_[$i][0] =~ /^GUI:/);
            push @opts, ((ref($_[$i]) eq 'ARRAY') ? $_[$i][0] : $_[$i]);
            push @opts, $_[$i+1] if ($cb == 2);
        }
    }
    return @opts;
}
